import json
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.edge.service import Service as EdgeService
from selenium.webdriver.edge.options import Options as EdgeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
import os
import platform
import traceback
import re
import openpyxl
from openpyxl.utils import get_column_letter
import tempfile
import uuid

def get_total_pages(driver):
    """
    获取分页器中的总页数
    基于携程分页器结构：<div class="m-reviewPagination"><ul class="m-pagination"><li class="m-pagination_numbers"><div class="m_num">1</div><div class="m_num m_num_checked">2</div>...</li></ul></div>
    """
    try:
        # 滚动到页面底部以确保分页器可见
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(2)
        
        # 尝试多种分页器选择器
        pagination_selectors = [
            ".m-reviewPagination .m-pagination_numbers",
            ".m-pagination .m-pagination_numbers", 
            ".m-pagination_numbers",
            ".pagination .numbers",
            ".pager .numbers"
        ]
        
        for selector in pagination_selectors:
            try:
                pagination_container = driver.find_element(By.CSS_SELECTOR, selector)
                # 获取所有页码元素（排除"..."元素）
                page_elements = pagination_container.find_elements(By.CSS_SELECTOR, ".m_num")
                
                if page_elements:
                    # 过滤掉"..."等非数字页码
                    valid_pages = []
                    for elem in page_elements:
                        page_text = elem.text.strip()
                        if page_text.isdigit():
                            valid_pages.append(int(page_text))
                    
                    if valid_pages:
                        total_pages = max(valid_pages)
                        print(f"通过选择器 {selector} 检测到总页数: {total_pages}")
                        return total_pages
                        
            except Exception as e:
                print(f"尝试选择器 {selector} 失败: {e}")
                continue
        
        # 如果上述方法失败，尝试通用方法
        try:
            # 查找所有可能的页码元素
            all_page_elements = driver.find_elements(By.XPATH, "//div[contains(@class, 'num') or contains(@class, 'page')][text()[normalize-space()]]")
            page_numbers = []
            
            for elem in all_page_elements:
                text = elem.text.strip()
                if text.isdigit() and 1 <= int(text) <= 100:  # 合理的页码范围
                    page_numbers.append(int(text))
            
            if page_numbers:
                total_pages = max(page_numbers)
                print(f"通过通用方法检测到总页数: {total_pages}")
                return total_pages
                
        except Exception as e:
            print(f"通用方法获取页数失败: {e}")
        
        print("未能检测到分页器，默认使用3页")
        return 3
        
    except Exception as e:
        print(f"获取总页数时出错: {e}，默认使用3页")
        return 3


def get_reviews_data(driver, max_pages=None):
    """
    获取酒店点评数据（支持分页）
    
    Args:
        driver: WebDriver实例
        max_pages: 最大获取页数，如果为None则自动获取总页数
    """
    all_reviews_data = []
    current_page = 1
    
    # 获取总页数
    total_pages = get_total_pages(driver)
    if max_pages is None:
        max_pages = total_pages
    else:
        max_pages = min(max_pages, total_pages)
    
    print(f"检测到总页数: {total_pages}，将获取前 {max_pages} 页")
    
    try:
        while current_page <= max_pages:
            print(f"\n=== 正在获取第 {current_page} 页点评数据 ===")
            
            # 滚动页面以加载更多内容
            print("正在滚动页面加载点评内容...")
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight/2);")
            time.sleep(3)
            
            # 尝试点击"查看更多点评"按钮
            more_buttons = [
                "//button[contains(text(), '更多')]",
                "//a[contains(text(), '更多')]", 
                "//span[contains(text(), '更多')]",
                "//button[contains(text(), '查看')]",
                "//a[contains(text(), '查看')]",
                "[class*='more']",
                "[class*='load']"
            ]
            
            for button_selector in more_buttons:
                try:
                    if button_selector.startswith('//'):
                        more_button = driver.find_element(By.XPATH, button_selector)
                    else:
                        more_button = driver.find_element(By.CSS_SELECTOR, button_selector)
                    if more_button.is_displayed():
                        driver.execute_script("arguments[0].click();", more_button)
                        print(f"点击了更多按钮: {button_selector}")
                        time.sleep(2)
                        break
                except:
                    continue
            
            # 尝试找到点评区域的多种可能选择器
            review_selectors = [
                ".comment-list",
                ".review-list", 
                ".reviews-container",
                "[data-testid='reviews']",
                ".user-comment",
                ".comment-item",
                ".review-item"
            ]
            
            review_container = None
            for selector in review_selectors:
                try:
                    review_container = driver.find_element(By.CSS_SELECTOR, selector)
                    print(f"找到点评容器: {selector}")
                    break
                except:
                    continue
            
            if not review_container:
                print("未找到点评容器，尝试查找单个点评元素...")
                
            # 尝试多种点评元素选择器
            review_item_selectors = [
                ".m-reviewCard",  # 新增：直接以 m-reviewCard 为点评单元
                ".comment-item",
                ".review-item",
                ".user-comment",
                "[class*='comment']",
                "[class*='review']",
                "[data-testid*='review']"
            ]
            
            review_elements = []
            for selector in review_item_selectors:
                try:
                    elements = driver.find_elements(By.CSS_SELECTOR, selector)
                    if elements:
                        review_elements = elements
                        print(f"找到 {len(elements)} 个点评元素，使用选择器: {selector}")
                        break
                except:
                    continue
            
            if not review_elements:
                print("未找到具体的点评元素，尝试通用方法...")
                # 尝试查找包含评分或点评关键词的元素
                try:
                    review_elements = driver.find_elements(By.XPATH, "//div[contains(text(), '点评') or contains(text(), '评价') or contains(@class, 'star') or contains(@class, 'rating')]")
                    if review_elements:
                        print(f"通过关键词找到 {len(review_elements)} 个可能的点评元素")
                except:
                    pass
            
            # 如果仍然没有找到，尝试调试模式
            if not review_elements:
                print("\n=== 调试模式：分析页面结构 ===")
                try:
                    # 查找所有可能包含文本的div元素
                    all_divs = driver.find_elements(By.TAG_NAME, "div")
                    text_divs = []
                    seen_texts = set()  # 用于去重
                    
                    for div in all_divs:
                        text = div.text.strip()
                        if text and len(text) > 20 and len(text) < 500:
                            # 检查是否是重复内容
                            if text not in seen_texts:
                                # 检查是否是父子元素重复（子元素的文本包含在父元素中）
                                is_duplicate = False
                                for existing_text in seen_texts:
                                    if text in existing_text or existing_text in text:
                                        is_duplicate = True
                                        break
                                
                                if not is_duplicate:
                                    text_divs.append(div)
                                    seen_texts.add(text)
                    
                    print(f"找到 {len(text_divs)} 个去重后的文本div元素")
                    
                    # 显示前几个作为示例
                    for i, div in enumerate(text_divs[:5]):
                        try:
                            class_name = div.get_attribute('class') or '无class'
                            text_preview = div.text[:100] + '...' if len(div.text) > 100 else div.text
                            print(f"示例 {i+1}: class='{class_name}', text='{text_preview}'")
                        except:
                            continue
                    
                    # 使用这些元素作为备选
                    review_elements = text_divs[:10]
                    print(f"使用前10个去重后的文本元素作为点评候选")
                    
                except Exception as debug_error:
                    print(f"调试模式出错: {debug_error}")
            
            # 获取当前页面的点评数据
            page_reviews = extract_reviews_from_page(driver, review_elements, current_page, len(all_reviews_data))
            all_reviews_data.extend(page_reviews)
            
            print(f"第 {current_page} 页获取到 {len(page_reviews)} 条点评")
            
            # 如果是最后一页或没有更多数据，退出循环
            if current_page >= max_pages:
                break
                
            # 尝试点击下一页
            if not click_next_page(driver):
                print("没有更多页面或无法点击下一页")
                break
                
            current_page += 1
            time.sleep(2)  # 等待页面加载

            # 关闭除主窗口外的所有新标签页（防止广告页弹出）
            main_window = driver.current_window_handle
            all_windows = driver.window_handles
            for handle in all_windows:
                if handle != main_window:
                    try:
                        driver.switch_to.window(handle)
                        driver.close()
                    except Exception as e:
                        print(f"关闭新标签页时出错: {e}")
            driver.switch_to.window(main_window)
        
        print(f"\n总共获取 {len(all_reviews_data)} 条点评数据（{current_page} 页）")
        return all_reviews_data
        
    except Exception as e:
        print(f"获取点评数据时出错: {e}")
        return all_reviews_data
        
        
def extract_reviews_from_page(driver, review_elements, page_num, start_index):
    """
    从当前页面提取点评数据
    """
    reviews_data = []
    seen_review_texts = set()  # 用于去重点评内容

    for i, element in enumerate(review_elements[:10]):  # 限制获取前10个点评
        try:
            review_data = {}

            # 1. 用户头像
            avatar_url = ""
            try:
                avatar_img = element.find_element(By.CSS_SELECTOR, ".m-reviewCard_userInfo .user img")
                avatar_url = avatar_img.get_attribute("src")
            except Exception:
                avatar_url = ""

            # 2. 用户名
            username = ""
            try:
                username_elem = element.find_element(By.CSS_SELECTOR, ".m-reviewCard_userInfo .info .name")
                username = username_elem.text.strip()
            except Exception:
                username = "匿名用户"

            # 3. 评分
            rating = ""
            try:
                rating_elem = element.find_element(By.CSS_SELECTOR, ".m-score_single strong")
                rating = rating_elem.text.strip()
            except Exception:
                rating = "未知"

            # 4. 评论内容
            review_text = ""
            try:
                review_text_elem = element.find_element(By.CSS_SELECTOR, ".comment .content-break-line")
                review_text = review_text_elem.text.strip()
            except Exception:
                review_text = element.text.strip()

            # 5. 发布时间
            review_date = ""
            try:
                review_date_elem = element.find_element(By.CSS_SELECTOR, ".reviewDate")
                review_date = review_date_elem.text.strip()
            except Exception:
                review_date = "未知"

            # 6. 入住时间
            checkin_date = ""
            try:
                # 查找包含日历图标的li元素
                checkin_elem = element.find_element(By.CSS_SELECTOR, ".user-left .other li i.u-icon-ic_new_calendar_line")
                # 获取父级li元素中的span文本
                checkin_li = checkin_elem.find_element(By.XPATH, "..")
                checkin_span = checkin_li.find_element(By.CSS_SELECTOR, "span")
                checkin_date = checkin_span.text.strip()
            except Exception:
                checkin_date = "未知"

            # 只保存有内容且不重复的点评
            if review_text and len(review_text) > 10:
                if review_text not in seen_review_texts:
                    review_data = {
                        "序号": start_index + len(reviews_data) + 1,
                        "用户名": username,
                        "用户头像": avatar_url,
                        "点评内容": review_text,
                        "评分": rating,
                        "点评时间": review_date,
                        "入住时间": checkin_date,
                        "页码": page_num
                    }
                    reviews_data.append(review_data)
                    seen_review_texts.add(review_text)
            
        except Exception as e:
            print(f"解析第 {i+1} 个点评时出错: {e}")
            continue

    return reviews_data


def click_next_page(driver):
    """
    尝试点击下一页按钮，基于携程分页器结构
    """
    try:
        # 基于用户提供的分页器HTML结构
        # <div class="m-reviewPagination"><ul class="m-pagination">
        # <li class="m-pagination_numbers"><div class="m_num m_num_checked">1</div><div class="m_num">2</div><div class="m_num">3</div><div class="m_num">4</div><div class="m_num">5</div><div class="m_num">...</div><div class="m_num">19</div></li>
        # <li class="m-pagination_item"><a class="forward active"><i type="arrowRight" class="u-icon u-icon-arrowRight"></i></a></li>
        
        # 尝试多种选择器来找到下一页按钮
        next_page_selectors = [
            # 基于携程分页器的具体结构
            ".m-reviewPagination .forward.active",
            ".m-pagination .forward.active", 
            ".m-pagination_item .forward.active",
            "a.forward.active",
            # 通用的下一页选择器
            "//a[contains(@class, 'forward') and contains(@class, 'active')]",
            "//a[contains(text(), '下一页')]",
            "//button[contains(text(), '下一页')]",
            ".pagination .next",
            ".pager .next",
            "[data-testid='next-page']"
        ]
        
        for selector in next_page_selectors:
            try:
                if selector.startswith('//'):
                    next_button = driver.find_element(By.XPATH, selector)
                else:
                    next_button = driver.find_element(By.CSS_SELECTOR, selector)
                
                # 检查按钮是否可见且可点击
                if next_button.is_displayed() and next_button.is_enabled():
                    # 滚动到按钮位置
                    driver.execute_script("arguments[0].scrollIntoView(true);", next_button)
                    time.sleep(1)
                    
                    # 点击按钮
                    driver.execute_script("arguments[0].click();", next_button)
                    print(f"成功点击下一页按钮: {selector}")
                    return True
            except Exception as e:
                print(f"尝试选择器 {selector} 失败: {e}")
                continue
        
        # 如果上述方法都失败，尝试查找包含箭头图标的元素
        try:
            arrow_elements = driver.find_elements(By.CSS_SELECTOR, "i.u-icon-arrowRight")
            for arrow in arrow_elements:
                parent = arrow.find_element(By.XPATH, "..")
                if parent.is_displayed() and parent.is_enabled():
                    driver.execute_script("arguments[0].scrollIntoView(true);", parent)
                    time.sleep(1)
                    driver.execute_script("arguments[0].click();", parent)
                    print("通过箭头图标成功点击下一页")
                    return True
        except Exception as e:
            print(f"通过箭头图标点击失败: {e}")
        
        print("未找到可用的下一页按钮")
        return False
        
    except Exception as e:
        print(f"点击下一页时出错: {e}")
        return False





def batch_get_reviews(hotel_id):
    """
    批量抓取 hotels_data.json 中所有酒店的点评数据，每家酒店生成一个独立 csv 文件
    """
    current_dir = os.path.dirname(os.path.abspath(__file__))
    project_root = os.path.dirname(current_dir)
    cookies_path = os.path.join(project_root, "asset", "cookies.json")
    csv_dir = os.path.join(project_root, "csv")
    os.makedirs(csv_dir, exist_ok=True)

    url = f"https://hotels.ctrip.com/hotels/detail/?cityId=17&checkIn=2025-07-14&checkOut=2025-07-15&hotelId={hotel_id}&adult=1&crn=1&children=0&highprice=-1&lowprice=0&listfilter=1"
    driver = None
    reviews_data = []
    hotel_name = "未知酒店"
    user_data_dir = None
    
    try:
        chrome_options = ChromeOptions()
        chrome_options.add_argument("--no-sandbox")
        chrome_options.add_argument("--disable-dev-shm-usage")
        chrome_options.add_argument("--disable-blink-features=AutomationControlled")
        chrome_options.add_argument("--disable-web-security")
        chrome_options.add_argument("--allow-running-insecure-content")
        # 稳定性和兼容性参数
        chrome_options.add_argument("--disable-extensions")
        chrome_options.add_argument("--disable-plugins")
        chrome_options.add_argument("--disable-gpu")  # 禁用GPU加速，提高稳定性
        chrome_options.add_argument("--remote-debugging-port=0")  # 随机端口，避免冲突
        chrome_options.add_argument("--disable-features=VizDisplayCompositor")  # 提高稳定性
        # chrome_options.add_argument("--disable-images")  # 可能影响页面加载，暂时注释
        # chrome_options.add_argument("--headless")  # 无头模式可能影响某些网站，暂时注释
        chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
        chrome_options.add_experimental_option('useAutomationExtension', False)
        
        # 创建更唯一的临时目录，使用时间戳和进程ID
        import time
        timestamp = str(int(time.time() * 1000))  # 毫秒级时间戳
        process_id = str(os.getpid())  # 当前进程ID
        unique_id = f"{timestamp}_{process_id}_{str(uuid.uuid4())[:8]}"
        user_data_dir = os.path.join(tempfile.gettempdir(), f"chrome_profile_{unique_id}")
        
        # 确保目录不存在，如果存在则删除
        if os.path.exists(user_data_dir):
            import shutil
            try:
                shutil.rmtree(user_data_dir)
                print(f"已清理旧的临时目录: {user_data_dir}")
            except Exception as e:
                print(f"清理旧目录失败: {e}")
                # 如果清理失败，创建新的目录名
                unique_id = f"{timestamp}_{process_id}_{str(uuid.uuid4())}"
                user_data_dir = os.path.join(tempfile.gettempdir(), f"chrome_profile_{unique_id}")
        
        chrome_options.add_argument(f"--user-data-dir={user_data_dir}")
        print(f"使用临时目录: {user_data_dir}")
        # 清理可能损坏的ChromeDriver缓存
        try:
            chromedriver_path = ChromeDriverManager().install()
            print(f"ChromeDriverManager返回路径: {chromedriver_path}")
            
            # 修复路径问题：ChromeDriverManager有时返回错误的文件
            if platform.system() == "Windows":
                # 查找正确的chromedriver.exe文件
                driver_dir = os.path.dirname(chromedriver_path)
                possible_paths = [
                    os.path.join(driver_dir, "chromedriver.exe"),
                    os.path.join(driver_dir, "chromedriver-win32", "chromedriver.exe"),
                    os.path.join(os.path.dirname(driver_dir), "chromedriver.exe"),
                    chromedriver_path if chromedriver_path.endswith(".exe") else None
                ]
                
                actual_driver_path = None
                for path in possible_paths:
                    if path and os.path.exists(path) and path.endswith(".exe"):
                        file_size = os.path.getsize(path)
                        if file_size > 1000:  # 确保文件不是空的或损坏的
                            actual_driver_path = path
                            print(f"找到有效的ChromeDriver: {actual_driver_path} (大小: {file_size} bytes)")
                            break
                
                if not actual_driver_path:
                    raise Exception("未找到有效的chromedriver.exe文件")
                
                chromedriver_path = actual_driver_path
            
            # 检查文件是否存在且可执行
            if not os.path.exists(chromedriver_path):
                raise Exception("ChromeDriver文件不存在")
            
            service = ChromeService(chromedriver_path)
            print(f"使用ChromeDriver: {chromedriver_path}")
            
        except Exception as driver_error:
            print(f"ChromeDriver安装/检查失败: {driver_error}")
            print("尝试使用系统PATH中的chromedriver...")
            service = ChromeService()  # 使用系统PATH中的chromedriver
        driver = webdriver.Chrome(service=service, options=chrome_options)
        driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
        driver.get("https://www.ctrip.com")
        time.sleep(2)
        with open(cookies_path, 'r', encoding='utf-8') as f:
            cookies = json.load(f)
        for cookie in cookies:
            try:
                cookie_dict = {
                    'name': cookie['name'],
                    'value': cookie['value'],
                    'domain': cookie['domain'],
                    'path': cookie.get('path', '/'),
                }
                if 'expiry' in cookie:
                    cookie_dict['expiry'] = cookie['expiry']
                if 'httpOnly' in cookie:
                    cookie_dict['httpOnly'] = cookie['httpOnly']
                if 'secure' in cookie:
                    cookie_dict['secure'] = cookie['secure']
                if 'sameSite' in cookie:
                    cookie_dict['sameSite'] = cookie['sameSite']
                driver.add_cookie(cookie_dict)
            except Exception as e:
                continue
        driver.get(url)
        time.sleep(2)
        # 解析酒店名称
        try:
            h1_elem = driver.find_element(By.CSS_SELECTOR, "h1.detail-headline_name")
            hotel_name = h1_elem.text.strip() if h1_elem else "未知酒店"
        except Exception:
            hotel_name = "未知酒店"
        print(f"\n==== 正在抓取: {hotel_name} (ID: {hotel_id}) ====")
        reviews_data = get_reviews_data(driver, max_pages=None)
    except Exception as e:
        print(f"抓取酒店 {hotel_name} 失败: {e}\n{traceback.format_exc()}")
        reviews_data = []
    finally:
        if driver:
            try:
                driver.quit()
                print("WebDriver 已关闭")
            except Exception as e:
                print(f"关闭 WebDriver 时出错: {e}")
        
        # 清理临时目录
        if user_data_dir and os.path.exists(user_data_dir):
            try:
                import shutil
                import time
                time.sleep(2)  # 等待Chrome完全关闭
                shutil.rmtree(user_data_dir)
                print(f"已清理临时目录: {user_data_dir}")
            except Exception as e:
                print(f"清理临时目录失败: {e}，请手动删除: {user_data_dir}")
    # 每抓取完一个酒店立即保存为 CSV
    safe_hotel_name = re.sub(r'[\\/:*?\"<>|]', '_', hotel_name)
    out_path = os.path.join(csv_dir, f"{safe_hotel_name}.xlsx")
    if reviews_data:

        wb = openpyxl.Workbook()
        ws = wb.active
        ws.title = "点评数据"
        fieldnames = ["序号", "酒店id", "用户名", "用户头像", "点评内容", "评分", "点评时间", "入住时间", "页码"]
        ws.append(fieldnames)
        for row in reviews_data:
            ws.append([
                row.get("序号", ""),
                hotel_id,
                row.get("用户名", ""),
                row.get("用户头像", ""),
                row.get("点评内容", ""),
                row.get("评分", ""),
                row.get("点评时间", ""),
                row.get("入住时间", ""),
                row.get("页码", "")
            ])
        # 自动调整列宽
        for col in ws.columns:
            max_length = 0
            col_letter = get_column_letter(col[0].column)
            for cell in col:
                try:
                    if cell.value:
                        max_length = max(max_length, len(str(cell.value)))
                except:
                    pass
            ws.column_dimensions[col_letter].width = max_length + 2
        wb.save(out_path)
        print(f"已保存: {out_path}，点评数: {len(reviews_data)}")
    else:
        print(f"未获取到点评数据，未生成 Excel 文件。")


if __name__ == "__main__":
    # 酒店id， 可以通过浏览器f12查看
    batch_get_reviews(80642532)


